greenplum OOM的难问题定位

背景描述

使用的环境是2+3,2台当计算节点,3台当计算节点,总共起了48个primary segment和48个mirror segment。该gp集群是业务程序项目测试使用,运行一段时间后,gp会出现突然宕机的情况。

问题定位

用户的最大进程数导致segment失效

查询pg_log日志,发现如下信息could not fork new process for connection: Resource temporarily unavailable。

当该日志反复出现后,会出现segment与gpmaster连接断开,认为segment已挂的情况,导致segment不可用。初步估计是一台机器上起了过多的segment,每次连接都会在segment上打开一个新的请求。
于是查看业务程序的gp连接数,发现连接数有240个之多,而配置的最大连接数为250个。虽然连接很多,但还没达到上限。通过ulimit -a查看系统配置参数,发现用户最大进程数存在问题。gpadmin用户的系统内核配置项max user processes (-u) 4096,和gp官方推荐的131072相差较大,在vi /etc/security/limits.conf 配置如下

1
2
3
4
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072

解决办法

nproc为用户进程数,可以看出最大进程数被配置为了131072,但是gpadmin用户的没有配置,于是追加两行

1
2
gpadmin soft nproc 131072
gpadmin hard nproc 131072

修改完gp集群各操作系统的该配置项后,不再报该错误。但第二天发现集群又有segment挂了,继续定位。

OOM导致segment挂了,致使集群不可用

看到日志could not fork new process for connection: Cannot allocate memory。出现这种错误,一般是由于操作系统无法给segment分配所需要的内存导致的。但gp的每个segment有个安全阈值,配置项gp_vmem_protect_limit的值,默认为8192,当segment使用的内存占用到了runaway_detector_activation_percent(默认90%)的值时候,就会拒绝掉新的sql请求,以此保证不会出现OOM的情况。gpadmin用户同样会收到该限制。那gp为什么会挂呢,检查gpconfig -s gp_vmem_protect_limit发现是默认值8192,按照官网上的建议配置,该值应该设置成一台机器上的(总内存+
swap 内存)0.90.5/主segmemnt数目 ,我们的机器一台是256GB内存,一台运行16个主segment,那所需要配置的内存应该是256GB0.90.5/16=7372M
.修改gp_vmem_protect_limit为7372MB,重启集群。但在业务程序开始使用的时候,再次出现segment挂了的情况。把该值调整成一半,再次挂掉。让我们分析下GP在哪些情况上可能出现OOM的情况。

OOM可能出现的情况。

当集群出现oom的时候,能在日志下看到类似内容:

1
2
"ERROR","53400","Out of memory (seg13 slice13 sdw1-1:40001 pid=10183)","VM Protect failed to allocate 8388608 bytes, 6 MB available"`
"ERROR","53200","Out of memory. Failed on request of size 156 bytes. (context 'CacheMemoryContext') (aset.c:840)"

避免集群出现OOM参数的关键配置项是gp_vmem_protect_limit
关于上面报错码为53400的,往往会有如下日志相伴出现:

  • "VM Protect failed to allocate %d bytes,%d MB available":这种情况一般是由于request请求所需要的结果集很大造成,例如查询需要返回的内容为10G,而segment的保护阈值为8G,就有可能出现该问题。
  • "Per-query VM protect limit reached: current limit is %d kB, requested %d bytes, available %d MB":一个请求超出极限:gp_vmem_protect_limit_per_query
  • "VM protect failed to allocate %d bytes from system, VM Protect %d MB available":这种情况是虽然segment看上去还有很多内存可申请,但操作系统已经无法分配更多内存给他了。当集群挂的时候,出现过错误allocate 1024 bytes,7000MB available的情况。虽然segment看上去可以申请很多内存,但操作系统已经无法进行内存分配。
  • "Failed to allocate memory under virtual memory protection" "Error %d, errno %d, %s": 该问题是由于应用的内存分配,具体不知。但与上面三个的原因不同。

官网给出的OOM解决办法

错误码53400
  1. 资源队列使用的配置的内存太大,超过了gp_vmem_protect_limit,如果是这种情况,需要把资源对列的最大可用内存调小。
  2. 超大的查询计划占用掉太多内存,这种情况需要设置gp_max_plan_size,来避免超大的查询计划出现
  3. gp_vmem_protect_limit设置不合理,如果设置的太高容易OOM,调低该值
  4. 有其他进程占用掉系统内存,导致程序分不到资源
错误码53200

segment的postmaster 进程不能从操作系统申请内存,需要配合系统日志一起分析解决。

解决我们的问题

问题分析

毫无疑问我们的错误是属于53400那种。业务程序最早使用的用户是gpadmin,连接数最多会接近max_connections值。当进行大量查询的时候就会出现OOM异常。而调小segment的内存值我们已经设置过了,并不生效。按照官网文档给的segment保护内存值,8192也是一个比较接近我们环境应该配置的值。于是尝试自己写了个并发程序来看gp到什么程度下会OOM。在自己的并发程序中。
模拟业务程序操作GP的方法,取5条业务程序执行的sql语句,该语句Limit 100000,每条sql语句都会占据较大的内存,语句执行完成后,不释放ResultSet,若干个线程反复执行,造成的情况如下。
同时起若干个线程,每个线程占用掉一个连接,每个线程循环执行若干次,出现了如下的情况。
ACTIVE_STATEMENTS=1,MEMORY_LIMIT='6000MB' 35个线程可以正常运行 ACTIVE_STATEMENTS=2,MEMORY_LIMIT='6000MB' 35个线程 一定时间后少量被取消。 ACTIVE_STATEMENTS=2,MEMORY_LIMIT='6000MB' 25个线程 一段时间后少量报错。 ACTIVE_STATEMENTS=2,MEMORY_LIMIT='6000MB' 20个线程 无报错。 ACTIVE_STATEMENTS=3,MEMORY_LIMIT='6000MB' 20个线程 无报错。 ACTIVE_STATEMENTS=4,MEMORY_LIMIT='6000MB' 20个线程 无报错。
当连接数和同时执行的sql达到一定值后,就会报OOM异常。初步猜测是有由于原先建立的连接占用的资源未释放,同时业务程序不停的创建新的连接下发sql来消耗掉大量的内存,直至申请不到内存,导致OOM异常。由于GP是不会去限制gpadmin的内存使用的,只要有空闲连接,gp就会允许gpadmin的sql语句往下执行。假如刚好某个segment内存已到瓶颈,此时gpadmin再申请一个连接,就有可能会报oom异常 can not fork process for new connection:can not allocate memory.当内存耗尽的时候,便报了segment oom错误,gp集群不可使用。

问题解决

我们创建一个资源队列,并把用户添加到该资源队列中。限制该资源对列允许使用的内存值为7000MB。

1
2
CREATE RESOURCE QUEUE i3 WITH (ACTIVE_STATEMENTS=5, MEMORY_LIMIT='7000MB');
CREATE ROLE i3user LOGIN RESOURCE QUEUE i3;

让业务程序使用非超级用户来访问GP,但由于业务程序会不停的申请使用gp的内存资源,把最大连接数也限制为25,以此避免业务程序操作gp时候创建大量连接消耗掉资源,当资源达到7000MB时候,便拒绝掉后续的请求。以此保证gp集群的正常可用。